/*
 * SPDX-FileCopyrightText: 2022-2024 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test

import android.app.NotificationManager
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.telecom.Call
import android.telecom.InCallService
import android.util.Log
import androidx.annotation.StringRes
import com.sanji.audio_test.output.OutputFile
import kotlin.collections.iterator
import kotlin.random.Random

/**
 * 通话录音服务类
 * 继承自InCallService，负责监听通话状态并控制录音过程
 */
class RecorderInCallService : InCallService(), RecorderThread.OnRecordingCompletedListener {
    companion object {
        private val TAG = RecorderInCallService::class.java.simpleName

        // 电话应用包名
        private const val PHONE_PACKAGE = "com.android.phone"

        // 各种操作动作常量
        private val ACTION_PAUSE = "${RecorderInCallService::class.java.canonicalName}.pause"
        private val ACTION_RESUME = "${RecorderInCallService::class.java.canonicalName}.resume"
        private val ACTION_RESTORE = "${RecorderInCallService::class.java.canonicalName}.restore"
        private val ACTION_DELETE = "${RecorderInCallService::class.java.canonicalName}.delete"
        // 额外数据键名
        private const val EXTRA_TOKEN = "token"
        private const val EXTRA_NOTIFICATION_ID = "notification_id"
    }

    // 主线程处理器
    private val handler = Handler(Looper.getMainLooper())
    // 通知管理器
    private lateinit var notificationManager: NotificationManager
    // 应用偏好设置
    private lateinit var prefs: Preferences
    // 通知管理类
    private lateinit var notifications: Notifications

    /**
     * 用于前台服务的通知ID。在整个服务生命周期中，它可能与不同的通话关联。
     * 直到所有录制器退出后才会被取消。
     */
    private val foregroundNotificationId by lazy {
        prefs.nextNotificationId
    }

    /**
     * 通知ID及其关联的录制器。这表示通知的期望状态。
     * 在调用[updateForegroundState]之前，它可能与实际状态不匹配。
     */
    private val notificationIdsToRecorders = HashMap<Int, RecorderThread>()

    /**
     * 通知状态数据类
     * @param titleResId 标题资源ID
     * @param message 消息内容
     * @param actionsResIds 操作按钮资源ID列表
     */
    private data class NotificationState(
        @StringRes val titleResId: Int,
        val message: String?,
        // 我们不存储意图，因为Intent没有重写equals()方法
        val actionsResIds: List<Int>,
    )

    /**
     * 当前显示的所有通知ID及其状态。这用于确定在从[notificationIdsToRecorders]中移除项目后
     * 应该取消哪些通知。状态用于仅在状态实际更改时应用更新。否则，如果超出速率限制
     * (每秒10次更新)，Android将阻止更新。
     */
    private val allNotificationIds = HashMap<Int, NotificationState>()

    /**
     * 每个活动通话的录制线程。当通话断开连接时，会立即从该映射中移除。
     */
    private val callsToRecorders = HashMap<Call, RecorderThread>()

    /**
     * 此服务实例接收的所有意图的令牌值。
     *
     * 对于暂停/恢复功能，我们不能使用绑定服务，因为[InCallService]
     * 使用其自己的不可扩展的[onBind]实现。因此，我们依赖于[onStartCommand]。
     * 但是，由于此服务需要被导出，意图可能来自第三方应用，我们不希望它们干扰录制。
     */
    private val token = Random.Default.nextBytes(128)

    // 通话状态回调
    private val callback = object : Call.Callback() {
        /**
         * 通话状态改变时的回调
         * @param call 通话对象
         * @param state 新的状态
         */
        override fun onStateChanged(call: Call, state: Int) {
            super.onStateChanged(call, state)
            Log.d(TAG, "onStateChanged: $call, $state")

            handleStateChange(call, state)
        }

        /**
         * 通话详情改变时的回调
         * @param call 通话对象
         * @param details 新的详情
         */
        override fun onDetailsChanged(call: Call, details: Call.Details) {
            super.onDetailsChanged(call, details)
            Log.d(TAG, "onDetailsChanged: $call, $details")

            handleDetailsChange(call, details)

            // 由于固件错误，在较旧的Samsung固件上，此回调(带有DISCONNECTED状态)
            // 是我们收到通话结束的唯一通知
            handleStateChange(call, null)
        }

        /**
         * 通话被销毁时的回调
         * @param call 被销毁的通话对象
         */
        override fun onCallDestroyed(call: Call) {
            super.onCallDestroyed(call)
            Log.d(TAG, "onCallDestroyed: $call")

            requestStopRecording(call)
        }
    }

    /**
     * 创建操作意图
     * @param notificationId 通知ID
     * @param action 操作动作
     * @return 配置好的意图对象
     */
    private fun createActionIntent(notificationId: Int, action: String): Intent =
        Intent(this, RecorderInCallService::class.java).apply {
            this.action = action
            // URI除了确保不同通知间的PendingIntents是唯一的外不用于其他任何用途
            // PendingIntent将仅在额外数据上不同的Intent视为相同的
            data = Uri.fromParts("notification", notificationId.toString(), null)
            putExtra(EXTRA_TOKEN, token)
            putExtra(EXTRA_NOTIFICATION_ID, notificationId)
        }

    /**
     * 服务创建时的回调
     */
    override fun onCreate() {
        super.onCreate()

        notificationManager = getSystemService(NotificationManager::class.java)
        prefs = Preferences(this)
        notifications = Notifications(this)
    }

    /** 处理来自通知操作的意图，用于暂停和恢复功能。 */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        try {
            val receivedToken = intent?.getByteArrayExtra(EXTRA_TOKEN)
            if (!receivedToken.contentEquals(token)) {
                throw IllegalArgumentException("无效的令牌")
            }

            val notificationId = intent?.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
            if (notificationId == -1) {
                throw IllegalArgumentException("无效的通知ID")
            }

            when (val action = intent?.action) {
                ACTION_PAUSE, ACTION_RESUME -> {
                    notificationIdsToRecorders[notificationId]?.isPaused = action == ACTION_PAUSE
                }
                ACTION_RESTORE, ACTION_DELETE -> {
                    notificationIdsToRecorders[notificationId]?.keepRecording =
                        if (action == ACTION_RESTORE) {
                            RecorderThread.KeepState.KEEP
                        } else {
                            RecorderThread.KeepState.DISCARD
                        }
                }
                else -> throw IllegalArgumentException("无效的操作: $action")
            }
        } catch (e: Exception) {
            Log.w(TAG, "处理意图失败: $intent", e)
        }

        // 所有操作都是一次性操作，如果发生重启不应重新传递
        stopSelf(startId)
        return START_NOT_STICKY
    }

    /**
     * 当电话框架意识到新通话时总是被调用。
     *
     * 这是新通话的入口点。[callback]总是被注册以跟踪状态变化。
     * 
     * @param call 新添加的通话对象
     */
    override fun onCallAdded(call: Call) {
        super.onCallAdded(call)
        Log.d(TAG, "onCallAdded: $call")

        // 回调在requestStopRecording()中注销
        call.registerCallback(callback)

        // 如果通话已经处于活动状态
        handleStateChange(call, null)
    }

    /**
     * 当电话框架销毁通话时调用。
     *
     * 这将请求取消录制，即使[call]恰好不处于断开连接状态之一。
     *
     * 这不能保证被调用，特别是在较旧的Samsung固件上，由于电话框架中的错误。
     * 因此，当通话进入断开连接状态时，[handleStateChange]会停止录制。
     * 
     * @param call 被移除的通话对象
     */
    override fun onCallRemoved(call: Call) {
        super.onCallRemoved(call)
        Log.d(TAG, "onCallRemoved: $call")

        // 无条件地请求停止录制，即使它不处于断开连接状态
        // 因为不会再收到该通话的进一步事件
        requestStopRecording(call)
    }

    /**
     * 根据[call]状态开始或停止录制。
     *
     * 如果状态是[Call.STATE_ACTIVE]，则开始录制。如果状态是[Call.STATE_DISCONNECTING]
     * 或[Call.STATE_DISCONNECTED]，则请求取消活动录制。如果[state]为空，
     * 则从[call]查询通话状态。
     * 
     * @param call 通话对象
     * @param state 通话状态
     */
    private fun handleStateChange(call: Call, state: Int?) {
        val callState = state ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            call.details.state
        } else {
            @Suppress("DEPRECATION")
            call.state
        }

        Log.d(TAG, "handleStateChange: $call, $state, $callState")

        if (call.parent != null) {
            Log.v(TAG, "忽略会议通话子项的状态变化")
        } else if (callState == Call.STATE_ACTIVE || (prefs.recordDialingState && callState == Call.STATE_DIALING)) {
            startRecording(call)
        } else if (callState == Call.STATE_DISCONNECTING || callState == Call.STATE_DISCONNECTED) {
            // 这是必要的，因为由于固件错误，onCallRemoved()可能不会被调用
            requestStopRecording(call)
        }

        callsToRecorders[call]?.isHolding = callState == Call.STATE_HOLDING
    }

    /**
     * 为[call]启动一个[RecorderThread]。
     *
     * 如果通话录制被禁用或未授予所需权限，则不会创建[RecorderThread]。
     *
     * 此函数是幂等的。
     * 
     * @param call 要录制的通话对象
     */
    private fun startRecording(call: Call) {
        if (!prefs.isCallRecordingEnabled) {
            Log.v(TAG, "通话录制已禁用")
        } else if (!Permissions.haveRequired(this)) {
            Log.v(TAG, "尚未授予所需权限")
        } else if (!callsToRecorders.containsKey(call)) {
            val callPackage = call.details.accountHandle.componentName.packageName
            if (callPackage != PHONE_PACKAGE && !prefs.recordTelecomApps) {
                Log.w(TAG, "忽略与包关联的通话: $callPackage")
                return
            }

            val recorder = try {
                RecorderThread(this, this, call)
            } catch (e: Exception) {
                notifications.notifyRecordingFailure(e.message, null, emptyList())
                throw e
            }
            callsToRecorders[call] = recorder

            val notificationId = if (notificationIdsToRecorders.isEmpty()) {
                foregroundNotificationId
            } else {
                prefs.nextNotificationId
            }
            notificationIdsToRecorders[notificationId] = recorder

            updateForegroundState()
            recorder.start()
        }
    }

    /**
     * 请求取消[RecorderThread]。
     *
     * [RecorderThread]会立即从[callsToRecorders]中移除，但会保留在
     * [notificationIdsToRecorders]中以保持前台服务活跃，直到[RecorderThread]
     * 退出并报告其状态。如果录制过程中发生错误，线程可能在调用此函数之前退出并从[callsToRecorders]中移除。
     *
     * 此函数还会从通话中注销[callback]，因为不再需要跟踪进一步的状态变化。
     *
     * 此函数是幂等的。
     * 
     * @param call 要停止录制的通话对象
     */
    private fun requestStopRecording(call: Call) {
        // 在AOSP实现和重度修改的构建(如Samsung固件)中多次调用是安全的
        // 如果这成为问题，我们可以跟踪哪些通话已注册回调
        call.unregisterCallback(callback)

        val recorder = callsToRecorders[call]
        if (recorder != null) {
            recorder.cancel()

            callsToRecorders.remove(call)

            // 直到线程退出前不要更改前台状态
        }
    }

    /**
     * 通知录制线程通话详情已更改。
     *
     * 录制线程使用通话详情生成文件名。
     * 
     * @param call 通话对象
     * @param details 通话详情
     */
    private fun handleDetailsChange(call: Call, details: Call.Details) {
        val parentCall = call.parent
        val recorder = if (parentCall != null) {
            callsToRecorders[parentCall]
        } else {
            callsToRecorders[call]
        }

        recorder?.onCallDetailsChanged(call, details)
    }

    /**
     * 当有活动通话或录制线程尚未完成退出时，移动到前台并创建持久通知。
     */
    private fun updateForegroundState() {
        if (notificationIdsToRecorders.isEmpty()) {
            stopForeground(STOP_FOREGROUND_REMOVE)
        } else {
            // 取消并移除已退出录制器的通知
            for (notificationId in allNotificationIds.keys.minus(notificationIdsToRecorders.keys)) {
                // 前台通知将被覆盖
                if (notificationId != foregroundNotificationId) {
                    notificationManager.cancel(notificationId)
                }
                allNotificationIds.remove(notificationId)
            }

            // 将前台通知重新分配给另一个录制器
            if (foregroundNotificationId !in notificationIdsToRecorders) {
                val iterator = notificationIdsToRecorders.iterator()
                val (notificationId, recorder) = iterator.next()
                iterator.remove()
                notificationManager.cancel(notificationId)
                allNotificationIds.remove(notificationId)
                notificationIdsToRecorders[foregroundNotificationId] = recorder
            }

            // 创建/更新通知
            for ((notificationId, recorder) in notificationIdsToRecorders) {
                val titleResId: Int
                val actionResIds = mutableListOf<Int>()
                val actionIntents = mutableListOf<Intent>()
                val canShowDelete: Boolean

                when (recorder.state) {
                    RecorderThread.State.NOT_STARTED -> {
                        titleResId = Call recording initializing
                        canShowDelete = true
                    }
                    RecorderThread.State.RECORDING -> {
                        if (recorder.isHolding) {
                            titleResId = "Call on hold"
                            // 持有时不允许更改暂停状态
                        } else if (recorder.isPaused) {
                            titleResId = "Call recording paused"
                            actionResIds.add("Resume")
                            actionIntents.add(createActionIntent(notificationId, ACTION_RESUME))
                        } else {
                            titleResId = "Call recording in progress"
                            actionResIds.add("Pause")
                            actionIntents.add(createActionIntent(notificationId, ACTION_PAUSE))
                        }
                        canShowDelete = true
                    }
                    RecorderThread.State.FINALIZING, RecorderThread.State.COMPLETED -> {
                        titleResId = "Call recording finalizing"
                        canShowDelete = false
                    }
                }

                val message = StringBuilder(recorder.outputPath.unredacted)

                if (canShowDelete) {
                    recorder.keepRecording?.let {
                        when (it) {
                            RecorderThread.KeepState.KEEP -> {
                                actionResIds.add("Delete")
                                actionIntents.add(createActionIntent(notificationId, ACTION_DELETE))
                            }
                            RecorderThread.KeepState.DISCARD -> {
                                message.append("\n\n")
                                message.append("The recording will be deleted at the end of the call. Tap Restore to keep the recording.")
                                actionResIds.add("Restore")
                                actionIntents.add(createActionIntent(notificationId, ACTION_RESTORE))
                            }
                            RecorderThread.KeepState.DISCARD_TOO_SHORT -> {
                                val minDuration = prefs.minDuration

                                message.append("\n\n")
                                message.append(resources.getQuantityString(
                                    R.plurals.notification_message_delete_at_end_too_short,
                                    minDuration,
                                    minDuration,
                                ))
                                actionResIds.add("Restore")
                                actionIntents.add(createActionIntent(notificationId, ACTION_RESTORE))
                            }
                        }
                    }
                }

                val state = NotificationState(
                    titleResId,
                    message.toString(),
                    actionResIds,
                )
                if (state == allNotificationIds[notificationId]) {
                    // 避免速率限制
                    continue
                }

                val notification = notifications.createPersistentNotification(
                    state.titleResId,
                    state.message,
                    state.actionsResIds.zip(actionIntents),
                )

                if (notificationId == foregroundNotificationId) {
                    startForeground(notificationId, notification)
                } else {
                    notificationManager.notify(notificationId, notification)
                }

                allNotificationIds[notificationId] = state
            }

            notifications.vibrateIfEnabled(Notifications.CHANNEL_ID_PERSISTENT)
        }
    }

    /**
     * 录制器退出时的处理
     * @param recorder 退出的录制器线程
     */
    private fun onRecorderExited(recorder: RecorderThread) {
        // 如果录制过程中发生错误，这可能是提前退出。从映射中移除以确保线程不会收到更多通话相关回调
        if (callsToRecorders.entries.removeIf { it.value === recorder }) {
            Log.w(TAG, "$recorder 在取消前退出")
        }

        // 不再需要显示通知。如果此录制器与前台服务通知关联，
        // updateForegroundState()会将foregroundNotificationId重新分配给另一个录制器
        assert(notificationIdsToRecorders.entries.removeIf { it.value === recorder }) {
            "未找到 $recorder"
        }

        updateForegroundState()
    }

    /**
     * 录制状态改变时的回调
     * @param thread 录制线程
     */
    override fun onRecordingStateChanged(thread: RecorderThread) {
        handler.post {
            updateForegroundState()
        }
    }

    /**
     * 录制完成时的回调
     * @param thread 录制线程
     * @param file 主要输出文件
     * @param additionalFiles 附加文件列表
     * @param status 录制状态
     */
    override fun onRecordingCompleted(
        thread: RecorderThread,
        file: OutputFile?,
        additionalFiles: List<OutputFile>,
        status: RecorderThread.Status,
    ) {
        Log.i(TAG, "录制完成: ${thread.id}: ${file?.redacted}: $status")
        handler.post {
            onRecorderExited(thread)

            when (status) {
                RecorderThread.Status.Succeeded -> {
                    notifications.notifyRecordingSuccess(file!!, additionalFiles)
                }
                is RecorderThread.Status.Failed -> {
                    val message = buildString {
                        when (status.component) {
                            is RecorderThread.FailureComponent.AndroidMedia -> {
                                val frame = status.component.stackFrame

                                append(getString("The recording failed in an internal Android component (%s). This device or firmware might not support call recording.",
                                    "${frame.className}.${frame.methodName}"))
                            }
                            RecorderThread.FailureComponent.Other -> {}
                        }

                        status.exception?.localizedMessage?.let {
                            if (isNotEmpty()) {
                                append("\n\n")
                            }
                            append(it)
                        }
                    }

                    notifications.notifyRecordingFailure(message, file, additionalFiles)
                }
                is RecorderThread.Status.Discarded -> {
                    when (status.reason) {
                        RecorderThread.DiscardReason.Intentional -> {}
                        is RecorderThread.DiscardReason.Silence -> {
                            notifications.notifyRecordingPureSilence(status.reason.callPackage)
                        }
                    }
                }
                RecorderThread.Status.Cancelled -> {}
            }
        }
    }
}